<?php
/**
* @package as_reportool - Report printing class
* as_reportool.php, main class for html report drawing
* @author Alexander Selifonov <as-works@narod.ru>
* @link http://www.selifan.ru
* @license http://www.gnu.org/copyleft/gpl.html
* modified 26.11.2008
* @version 1.00.002
**/
define('ASREPT_ERR_WRONGXMLFILE', -101);
define('ASREPT_ERR_WRONGFORMAT', -102);
/**
* @desc CReportField - one reporting field definition
*/
$_lbpath = defined('LIBPATH')? LIBPATH: '';
@require_once($_lbpath.'as_dbutils.php'); # db access wrapper class
class CReportField {
  var $fieldno;
  var $title; # title for this field
  var $summable;
  var $fconv; # '' or function name to make presentation string
  var $format; # r=right, c=center, money= "money fmt",...
  function CReportField($title='',$summable=0, $fconv='', $format='') {
    $this->title=$title;
    $this->summable=$summable;
    $this->fconv=$fconv;
    $this->format=$format;
  }
}
class CReporTool { // main class def.
  var $_query = ''; # main query, that gets all info for report
  var $_headers = ''; # header rows for the top header
  var $_rfields = array(); # fields to draw or calculate
  var $_grpfields = array();
  var $_sumfields = array();
  var $_totals = array(); # internal use - accumulated totals
  var $_summary = array();
  var $_grp_curval = array();
  var $_supress_eg = true; # supress printing headers and subtotals for zeroed grouping field values
  var $_debug = 0; # set to positive integer N to break reporting after N-th line
  var $_summarytitle = ''; # if not empty, summary totals line will be printed at report's bottom
  var $_fontstyles = '';
  var $_rowcount = 0;
  var $errorcode = 0;
  var $errormessage = 0;
  var $_delim_tho = ','; # delimiters for number_format
  var $_delim_dec = '.';
  var $_outcharset = '';
  var $_incharset = '';
  var $_nodefhead = 0;
  var $_suppresscss = false;
  /**
  * constructor
  *
  * @param string $filename passed XML file name to load report definition from
  * @return CReporTool
  */
  function CReporTool($filename=false, $cset=null) {
    if(is_string($filename) && file_exists($filename) && function_exists('simplexml_load_file')) {
      $this->LoadFromXml($filename,$cset);
    }
  }
  function LoadFromXml($filename, $outcharset=null) {
    # parse xml file and load all report parameters
    ini_set('zend.ze1_compatibility_mode', 0);
    $xml = @simplexml_load_file($filename);
    $this->_incharset='UTF-8';
    if($outcharset!==null) $this->_outcharset = $outcharset;
    if(!$xml) {
#      echo '<pre>'.htmlspecialchars(file_get_contents($filename)).'</pre>';
      $this->errorcode = ASREPT_ERR_WRONGXMLFILE;
      $this->errormessage = "$filename has wrong XML format or non XML at all!";
      echo "debug print: error {$this->errorcode} - {$this->errormessage}<br />";
      return false;
    }
    $this->errorcode=0;  $this->errormessage='';
    $goodfmt = false;
    if(isset($xml->headings)) {
      $header = $this->DecodeCharValue($xml->headings);
      $this->SetHeadings($header);
    }
    foreach($xml->children() as $cid=>$obj) {
      switch($cid) {
        case 'query':
          $this->SetQuery($obj);
          break;
        case 'grpfield':
          $fldid = isset($obj['name'])?  trim($obj['name']) : '';
          $fconv = isset($obj['fconv'])? trim($obj['fconv']) : '';
          $title = isset($obj['title'])? $this->DecodeCharValue($obj['title']) : '';
          $ttitle = isset($obj['totaltitle'])? $this->DecodeCharValue($obj['totaltitle']) : '';
          if(!empty($fldid)) { $this->AddGroupingField($fldid,$fconv,$title,$ttitle); }
          break;
        case 'field':
          $fldid = isset($obj['name'])?  trim($obj['name']) : '';
          $title = isset($obj['title'])? $this->DecodeCharValue($obj['title']) : '';
          $summa = isset($obj['summable'])? trim($obj['summable']) : '';
          $fconv = isset($obj['fconv'])? trim($obj['fconv']) : '';
          $fmt = isset($obj['format'])? trim($obj['format']) : '';
          if(!empty($fldid)) {
            $this->AddField($fldid,$title,$summa,$fconv, $fmt);
            $goodfmt = true;
          }
          break;
        case 'nodefaultheadings':
          $this->_nodefhead = isset($obj['value'])? trim($obj['value']) : 0;
          break;
        case 'fontstyles':
          $this->_fontstyles = (!empty($obj['value']))? $obj['value'] : '';
          break;
        case 'delimiters':
          $dec = (!empty($obj['decimal']))? $obj['decimal'] : '';
          $tho = (!empty($obj['thousand']))? $obj['thousand'] : '';
          if($dec) $this->_delim_dec = $dec;
          if($tho) $this->_delim_tho = $tho;
          break;

        case 'summary':
          $title = (!empty($obj['title']))? $this->DecodeCharValue($obj['title']) : 'summary';
          $this->SetSummary($title);
          break;
      }
    }

    if(!$goodfmt) {
      $this->errorcode = ASREPT_ERR_WRONGFORMAT;
      $this->errormessage = "$filename is not AS_REPORTOOL or empty XML file !";
    }
    unset($xml);
    return true;
  }
  function DecodeCharValue($strg) {
    $ret = $strg;
    if($this->_outcharset!='' && $this->_outcharset!='UTF-8'  && function_exists('mb_convert_encoding'))
        $ret = @mb_convert_encoding($ret,$this->_outcharset,'UTF-8');
    return $ret;
  }
  function SetCharSet($par) {
    $this->_outcharset=strtoupper($par);
    if(count($this->_rfields)>0 && $this->_outcharset!='' && $this->_incharset!='' && $this->_outcharset!=$this->_incharset)
    {
      @mb_convert_variables($this->_outcharset,'UTF-8',$this->_rfields, $this->_grpfields);
      $this->_summarytitle = mb_convert_encoding($this->_summarytitle,$this->_outcharset,'UTF-8');
    }
  }
  /**
  * register one field to draw in report page
  *
  * @param mixed $fldid field name in the query
  * @param mixed $fldtitle title in the headers for the field
  * @param mixed $summable if not 0, make sub-totals for this field
  * @param mixed $fconv function name for converting field value to something readable, if needed (name for id etc.)
  */
  function AddField($fldid,$fldtitle='',$summable=0,$fconv='', $format='') {
    # if(empty($fldtitle)) $fldtitle=$fldid;
    $this->_rfields[$fldid] = new CReportField($fldtitle,$summable,$fconv,$format);
    if($summable) $this->_sumfields[] = $fldid;
  }
  function SetHeaders($par,$no_defaultheader=0) { $this->_headers = $par; $this->_nodefhead=$no_defaultheader; }
  function SetHeadings($par,$no_defaultheader=0) { $this->_headers = $par; $this->_nodefhead=$no_defaultheader; }
  function SetQuery($sqlquery) { $this->_query = $sqlquery; }
  function SetSummary($par='Total summary:') { $this->_summarytitle = $par; }
  function SetFontStyles($par='') { $this->_fontstyles = $par; }
  function AddGroupingField($fldid,$fconv='',$title='',$totaltitle='') {
    $this->_grpfields[$fldid] = array('fconv'=>$fconv,'title'=>$title,'ttitle'=>$totaltitle);
  }

  /**
  * sets decimal and thousand delimiters for number_format()
  *
  * @param mixed $dec
  * @param mixed $tho
  */
  function SetNumberDelimiters($dec,$tho='') {
    $this->_delim_dec = $dec;
    $this->_delim_tho = $tho;
  }
  function SuppressCss($par=true) { $this->_suppresscss = $par; }
  /**
  * runs SQL query and echoes resulting report
  *
  */
  function DrawReport($title='') {
    global $as_dbengine;
    $this->_grp_curval = array();
    $this->_totals = array();
    foreach($this->_grpfields as $grpfld=>$grp) $this->_grp_curval[$grpfld]='{*}';
    foreach($this->_rfields as $fid=>$fld) { # init subtotals values array
      $this->_totals[$fid] = array();
      foreach($this->_sumfields as $sumf) { $this->_totals[$fid][$sumf] = 0; }
    }
    foreach($this->_sumfields as $sumf) { $this->_summary[$sumf] = 0; }
    $fnt = ($this->_fontstyles=='')? '': $this->_fontstyles;
    if(!$this->_suppresscss) {
?>
<style type="text/css">
/** styles for report drawing **/
   td.rep_ltrb { border: 1px solid #000000; text-align:center; <?=$fnt?>}
   td.rep_lrb { border-left:1px solid #000000; border-top:none; border-right: 1px solid #000000; border-bottom: 1px solid #000000; text-align:left; <?=$fnt?>}
   td.rep_rb  { border-left:none; border-top:none; border-right: 1px solid #000000; border-bottom: 1px solid #000000; text-align:left; <?=$fnt?> }
   td.num { text-align: right; font-weight: bold; };
   td.cnt { text-align: center; };
   td.newgroup { background: #EAEAEA; <?=$fnt?> }
</style>
<?
    }
    $colsp = count($this->_rfields);
    echo "<h4 style='text-align:center; font-weight:bold;'>$title</h4>\n<div id=\"divreport\" style=\"text-align:center;\"><table class=\"tablesorter\" id=\"minhaTabela\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">{$this->_headers}";

    # draw report field headers
    if(empty($this->_nodefhead)) {
      $thead = '<thead><tr>';
      foreach($this->_rfields as $fid=>$fld) {
        if($fld->title!='') $thead .="<th class=\"rep_ltrb\">{$fld->title}</th>";
      }
      $thead .='</tr></thead>';
      echo $thead;
    }

    $rlink = $as_dbengine->sql_query($this->_query);
    $this->_rowcount = 0;
    while(is_resource($rlink) && ($r=$as_dbengine->fetch_assoc($rlink))) {
      # check if some "grouping" field values changed, draw subtotals if so
      $this->_rowcount++;
      $grp_ffield = ''; # becomes a name of the "major" grouping field that has changed
      foreach($this->_grp_curval as $fid=>$curval) {
        if(isset($r[$fid]) && $curval !== $r[$fid]) {
          if($curval!=='{*}') { # draw and reset all accumulated sub-totals
            $this->__DrawSubtotals($fid);
          }
          $this->_grp_curval[$fid]=$r[$fid];
          $grp_ffield=$fid;
          $b_tmp = false;
          foreach($this->_grpfields as $grid=>$grp) {
            if($grid==$grp_ffield) { $b_tmp=true; continue; }
            if($b_tmp) $this->_grp_curval[$grid]='{*}';
          }
          if(!$b_tmp) $grp_ffield='';
          break;
        }
      }
      if($grp_ffield!=='') {
        # draw headers for next sub-total group
        if($this->_debug>0 && $this->_rowcount >= $this->_debug) break; # debug stop
        $ingrp = false;
        $leftoff='';
        foreach($this->_grpfields as $fid=>$grp) {
          if($fid==$grp_ffield) $ingrp=true;
          if($ingrp) {
            if(($this->_supress_eg) && empty($r[$fid])) continue; # don't print header for empty grouping value
            $fldval = (!empty($grp['fconv']) && function_exists($grp['fconv']))? call_user_func($grp['fconv'],$r[$fid],$r): $r[$fid];
            $txt = $grp['title'].' '.$fldval;
            $colsp = count($this->_rfields);
            echo "<tr><td class=\"rep_lrb newgroup\" colspan=\"$colsp\">{$leftoff}{$txt}</td></tr>\n";
            $this->_grp_curval[$fid]=$r[$fid];
          }
          $leftoff.=' &nbsp;&nbsp;';
        }
      }
      # now draw "normal" report row
      $strow = '<tr>';
      $colno = 0;
      foreach($this->_rfields as $fid=>$fld) {
        $cls = (++$colno==1)? 'rep_lrb':'rep_rb';
        if(in_array($fld->format, array('c','d'))) $cls .= ' cnt';
        elseif(in_array($fld->format, array('r','right','money','i'))) $cls .= ' num';

        if(isset($r[$fid])) {
          if (!empty($fld->fconv) && function_exists($fld->fconv)) $value=call_user_func($fld->fconv,$r[$fid],$r);
          else $value = $this->FormatValue($fid,$r[$fid]);
          if(in_array($fid,$this->_sumfields)) {
            $this->_summary[$fid] +=floatval($r[$fid]);
            # if(!isset($this->_totals[$fid])) $this->_totals[$fid] = 0;
            foreach($this->_grpfields as $grpid=>$gr) {
              if(!isset($this->_totals[$grpid][$fid])) $this->_totals[$grpid][$fid]=0;
              $this->_totals[$grpid][$fid] += floatval($r[$fid]);
            }
#            $cls .= ' num';
          }
        }
        else {
          $value = @function_exists($fld->fconv)? call_user_func($fld->fconv,0,$r) : "($fid)";
        }
        $strow .= "<td class=\"$cls\">&nbsp;$value</td>";
      }
      $strow .= "</tr>\n";
      echo $strow;
    }
    if(is_resource($rlink)) $as_dbengine->free_result($rlink);
    if(count($this->_grpfields)) $this->__DrawSubtotals();
    if(!empty($this->_summarytitle)) $this->__DrawSummaryTotals();
    echo "</table></div>";
  }
  /**
  * internal function, draws all sub-totals from lowest level to $gfield level
  *
  * @param string $gfield upper level grouping field
  */
  function __DrawSubtotals($gfield='') {
    $grfields = array_reverse($this->_grpfields); # go up from "inner" subtotal level
    $b_draw= true;
    foreach($grfields as $fid=>$grp) {
      # .. draw sub-total row;
      if(($this->_supress_eg) && empty($this->_grp_curval[$fid])) continue; # don't print subtotals if empty grouping value

      $ttval = $this->FormatValue($fid,$this->_grp_curval[$fid], $grp['fconv']);
      $ttpl = empty($grp['ttitle']) ? 'Total for %name%': $grp['ttitle'];
      $totitle = str_replace('%name%',$ttval,$grp['ttitle']);
      $cspan=0;
      foreach($this->_rfields as $fldid=>$fld) { if($fld->summable) break; $cspan++; }
      if($cspan<1) $txt = "<tr><td class=\"rep_lrb\" colspan=10 >$totitle</td></tr><tr>";
      else $txt = "<tr><td class=\"rep_lrb\" colspan=\"$cspan\">$totitle</td>";

      $b_strt = false;
      foreach($this->_rfields as $flddid=>$fld) {
        if(!$fld->summable && !$b_strt) continue;
        if($fld->summable) {
          $b_strt = true;
          $value = $this->FormatValue($flddid,$this->_totals[$fid][$flddid]);
          $txt .= "<td class=\"rep_rb num\" >$value</td>";
          $this->_totals[$fid][$flddid] = 0;
        }
        else $txt .= '<td class="rep_rb num">&nbsp;</td>';
      }
      $txt .="</tr>\n";
      echo $txt;
      if(!empty($gfield) && $fid==$gfield) break; # upper level of subtotal reached
    }
  }
  function __DrawSummaryTotals() {
      $cspan=0;
      $title = str_replace('%rowcount%',$this->_rowcount,$this->_summarytitle);
      foreach($this->_rfields as $fldid=>$fld) { if($fld->summable) break; $cspan++; }
      $colcnt = count($this->_rfields);
      if($cspan<1) $txt = "<tr><td class=\"rep_lrb\" colspan=\"$colcnt\" >$title</td></tr><tr>";
      else $txt = "<tr><td class=\"rep_lrb\" colspan=\"$cspan\">$title</td>";

      $b_strt = false;
      foreach($this->_rfields as $flddid=>$fld) {
        if(!$fld->summable && !$b_strt) continue;
        if($fld->summable) {
          $b_strt = true;
          $value = $this->FormatValue($flddid,$this->_summary[$flddid]);
          $txt .= "<td class=\"rep_rb num\" >$value</td>";
        }
        else $txt .= '<td class="rep_rb">&nbsp;</td>';
      }
      $txt .="</tr>\n";
      echo $txt;
  }

  function FormatValue($fieldid, $value) {
    $conv = !empty($this->_rfields[$fieldid]->fconv) ? $this->_rfields[$fieldid]->fconv :
     (!empty($this->_grpfields[$fieldid]['fconv']) ? $this->_grpfields[$fieldid]['fconv']:'');
    $fmt =  !empty($this->_rfields[$fieldid]->format) ? $this->_rfields[$fieldid]->format : '';
    if(!empty($conv) && function_exists($conv)) $ret = call_user_func($conv,$value);
    elseif($fmt=='money') $ret = number_format($value,2,$this->_delim_dec, $this->_delim_tho);
    elseif($fmt=='i') $ret = number_format($value,0,$this->_delim_dec, $this->_delim_tho);
    else $ret = $value;
    return $ret;
  }
} # CReporTool end
?>
